【GitHub Actions】cdk-nagのテスト結果をPRコメントするワークフローを作成してみた

【GitHub Actions】cdk-nagのテスト結果をPRコメントするワークフローを作成してみた

Clock Icon2024.12.31

cdk-nag を利用していると、以下のような悩みが出てこないでしょうか。

  • cdk-nag のセキュリティチェック結果を、プルリクエストのコメントで確認したい
  • エラーやワーニングの修正点が整理された状態で確認したい

私も cdk-nag のテストを GitHub Actions を動かしていたのですが、どのようなエラーが発生したのかを Actions のページで確認していました。
これがメンバーごと行うことを考えると、効率的ではないですよね。

そんな悩みを解決するため本記事では、プルリクエストを作成した際にテスト結果をコメントするワークフローを作成してみたので紹介します。

cdk-nag の詳細については、以下ブログを参照ください。

https://dev.classmethod.jp/articles/aws-cdk-compliance-check-with-cdk-nag/
https://dev.classmethod.jp/articles/add-cdk-nag-security-checks-to-github-actions/

前提

  • cdk-nag のテストがnpm run testで動作する
  • cdk-nag の出力メッセージを整形している

先述したブログが前提となっていますので、こちらを参照して設定してください。

作成したワークフロー

先に作成した GitHub Actions のワークフローを載せておきます。
こちらのコードは以下のリポジトリでも確認できます。
https://github.com/jun-suzuki1028/cdk-nag-test

name: cdk-nag Test

on:
  pull_request:
    branches: [main]

permissions:
  contents: read
  issues: write
  pull-requests: write

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Use Node.js 20
        uses: actions/setup-node@v3
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        id: run-tests
        continue-on-error: true
        run: |
          npm test 2>&1 | tee test-output.log
          exit_code=${PIPESTATUS[0]}
          echo "exit_code=$exit_code" >> $GITHUB_OUTPUT
          exit $exit_code

      - name: Create GitHub Comment
        uses: actions/github-script@v6
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            const fs = require('fs');

            const testOutput = fs.readFileSync('test-output.log', 'utf8');
            const errorMatches = testOutput.match(/\[error at .+?\] .+?(?=\n|$)/g);
            const warningMatches = testOutput.match(/\[warning at .+?\] .+?(?=\n|$)/g);

            let commentBody;
            if (errorMatches) {
              commentBody = `### 🚫 cdk-nag セキュリティチェック結果:対応が必要な問題があります

              以下の項目について修正が必要です:

              ${errorMatches.map(error => `- ${error}`).join('\n')}

              上記の問題点について、セキュリティとベストプラクティスの観点から修正してください。`;
            }

            if (warningMatches) {
              const warningSection = `### ⚠️ cdk-nag セキュリティチェック結果:確認推奨事項

              以下の項目について、確認してください:

              ${warningMatches.map(warning => `- ${warning}`).join('\n')}

              これらの警告は必ずしも修正が必要というわけではありませんが、
              より安全なインフラ構築のため、可能な範囲での対応を検討してください。`;

              commentBody = commentBody
                ? `${commentBody}\n\n${warningSection}`
                : warningSection;
            }

            if (!errorMatches && !warningMatches) {
              commentBody = `### ✅ cdk-nag セキュリティチェック結果:問題なし

              セキュリティチェックが完了し、問題は検出されませんでした。`;
            }

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: commentBody
            });

      - name: Fail if tests failed
        if: steps.run-tests.outputs.exit_code != 0
        run: exit 1

実行結果例

プルリクエストを作成すると、以下のようなコメントがされます。

成功

スクリーンショット 2024-12-31 午前8.30.06.png

エラー

スクリーンショット 2024-12-31 午前8.21.15.png

ワーニング

スクリーンショット 2024-12-31 午前8.24.58.png

エラーとワーニング

スクリーンショット 2024-12-31 午前8.27.40.png

詳細

ワークフローの詳細について簡単に解説します。

テストの実行結果保存

以下のステップでnpm testを実行し、その結果をtest-output.logに保存しています。

      - name: Run tests
        id: run-tests #あとで参照するためのID
        continue-on-error: true #エラーでも後続処理を実行
        run: |
          npm test 2>&1 | tee test-output.log
          exit_code=${PIPESTATUS[0]}
          echo "exit_code=$exit_code" >> $GITHUB_OUTPUT
          exit $exit_code

PIPESTATUSは各コマンドの終了ステータスを含む配列になっています。今回はnpm testの終了ステータスのみが入っており、[0]を指定することで終了コードを取得しています。

この終了コードは後続で使用したいので、$GITHUB_OUTPUTの環境変数へ保存します。

テストの成功、失敗を反映させるためexit $exit_codeとしていますが、continue-on-error: trueを設定しているためエラーでも後続処理が実行されます。

コメント追加

プルリクエストに対してコメントを追加するステップです。
先ほど保存したテストの実行結果を読み取り、内容に合わせてコメントを作成します。

コメントについては、適宜環境に合わせて修正してください。

      - name: Create GitHub Comment
        uses: actions/github-script@v6
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            const fs = require('fs');

            const testOutput = fs.readFileSync('test-output.log', 'utf8');
            const errorMatches = testOutput.match(/\[error at .+?\] .+?(?=\n|$)/g);
            const warningMatches = testOutput.match(/\[warning at .+?\] .+?(?=\n|$)/g);

            let commentBody;
            if (errorMatches) {
              commentBody = `### 🚫 cdk-nag セキュリティチェック結果:対応が必要な問題があります

              以下の項目について修正が必要です:

              ${errorMatches.map(error => `- ${error}`).join('\n')}

              上記の問題点について、セキュリティとベストプラクティスの観点から修正してください。`;
            }

            if (warningMatches) {
              const warningSection = `### ⚠️ cdk-nag セキュリティチェック結果:確認推奨事項

              以下の項目について、確認してください:

              ${warningMatches.map(warning => `- ${warning}`).join('\n')}

              これらの警告は必ずしも修正が必要というわけではありませんが、
              より安全なインフラ構築のため、可能な範囲での対応を検討してください。`;

              commentBody = commentBody
                ? `${commentBody}\n\n${warningSection}`
                : warningSection;
            }

            if (!errorMatches && !warningMatches) {
              commentBody = `### ✅ cdk-nag セキュリティチェック結果:問題なし

              セキュリティチェックが完了し、問題は検出されませんでした。`;
            }

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: commentBody
            });

errorMatcheswarningMatchesはそれぞれ正規表現を使って検索しています。ここで検索している文字列は整形後の出力となるため、デフォルトの cdk-nag の出力では動作しない点に注意してください。

その後のlet commentBody;以下はマッチした文字列に合わせてコメントを作成しています。
最後に作成したコメントを body に設定してプルリクエストに対しコメントします。

テスト失敗時のエラーハンドリング

最後に cdk-nag のテストで失敗している場合に、このワークフロー自体も失敗にしています。npm testの終了コードが 0 以外、つまりエラーがあった場合はエラーコード 1 で終了します。

      - name: Fail if tests failed
        if: steps.run-tests.outputs.exit_code != 0
        run: exit 1

前のステップ(Run tests)でcontinue-on-error: trueとしているため、このコードがない場合は必ず正常終了になってしまいます。そのため、ここで出力された終了コードを参照することで、ワークフロー全体の成功・失敗をハンドリングしています。

そのため、テストで失敗したコードがマージされることを防ぐことができます。

注意点

今回は検証のためnpm testで cdk-nag を実行しています。もし他のテスト(CDK のスナップショット等)がある場合は、npm script を分割して、別々のステップで実行するように修正することをお勧めします。

以下はスナップショットと実行コマンドを分ける例です。

package.json
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "test:cdk-nag": "jest test/cdk-nag",
    "test:snap": "jest test/snapshot",
    "cdk": "cdk"
  },

まとめ

cdk-nag のテスト結果をプルリクエストにコメントするワークフローを紹介してみました。
プルリクエストにコメントすることで、開発者がスムーズにエラー内容を把握できます。ぜひ開発フローに取り入れてみてください。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.